#include "Kobold.h"


#include <limits>

#include <iostream>
#include <fstream>
#include <string>

#include <cstdlib>

using namespace std;

//quickly take log base 2 of an integer
//handy for finding one member of a bitboard
static int log2(unsigned long long num)
{
  union
  {
    double f;
    int i;
  };
  f = num;
  i >>= 23;
  return i - 127;
}


//Screw vectors, if we're looking 16384 positions ahead, somethings wrong and I want some meaningful output.
static Killer killers[16384];

//material costs as suggested by Larry Kaufman
static int values[] = {0, 1000, 3250, 3250, 5000, 9750, 0,  -1000,  -3250, -3250, -5000, -9750, 0};

static int pawnValues[2][64] =
{ {  00,  00,  00,  00,  00,  00,  00,  00,
   -110,  80, 120, 160, 160, 120,  40,  -110,
   -110,  80, 120, 160, 160, 120,  40,  -110,
   -130,  20, 120, 160, 160, 120,  20,  -130,
   -150,  00,  00, 250, 250,  00,  00,  -150,
   -170, -20, -20,  60,  60, -20, -20,   -170,
   -100,  50,  50,-100,-100,  50,  50,  -100,
    00,  00,  00,  00,  00,  00,  00,  00},
{   00,  00,  00,  00,  00,  00,  00,  00,
   -100,  50,  50,-100,-100,  50,  50,  -100,
   -170, -20, -20,  60,  60, -20, -20,   -170,
   -150,  00,  00, 250, 250,  00,  00,  -150,
   -130,  20, 120, 160, 160, 120,  20,  -130,
   -110,  80, 120, 160, 160, 120,  40,  -110,
   -110,  80, 120, 160, 160, 120,  40,  -110,
    00,  00,  00,  00,  00,  00,  00,  00} };

static double evaluatePawns(ChessBoard* position)
{
  double value = 0;
  
  for(int i = 0; i < 8; i++)
  {
    for(int j = 0; j < 8; j++)
    {
      if(position->getPiece(i,j) == W_PAWN)
      {
        value += pawnValues[0][i*8 +j];
      }
      else if(position->getPiece(i,j) == B_PAWN)
      {
        value -= pawnValues[1][i*8 +j];
      }
    }
  }
  
  return value;
}

static double evaluateBishops(ChessBoard* position)
{
  double value = 0;
  
  int w, b;
  
  for(int i = 0; i < 8; i++)
  {
    for(int j = 0; j < 8; j++)
    {
      if(position->getPiece(i,j) == W_BISHOP)
      {
        w++;
      }
      else if(position->getPiece(i,j) == B_BISHOP)
      {
        b++;
      }
    }
  }
  
  if(w > 1) value += 500;
  if(b > 1) value += 500;

  //insert cool stuff here
  

  return value;
}

double Kobold::evaluate(ChessBoard* position)
{
  double value = 0;
  int pieces = 0;
  for(int i = 0; i < 8; i++)
  {
    for(int j = 0; j < 8; j++)
    {
      pieces += values[position->getPiece(i,j)];
    }
  }
  value += pieces;
  if(smart)
  {
    value += evaluatePawns(position);
    value += evaluateBishops(position);
  }
  if(position->minPlayerTurn())
    value *= -1;
  return value;
}

Move* Kobold::open(short size, Move* moves)
{
  char fromCol, fromRow, toCol, toRow;
  string command = "./open ./book.bin \"";
  command += position->getFen();
  command += "\" > move.txt";
  system(command.c_str());
  ifstream input("move.txt");
  input >> fromCol >> fromRow >> toCol >> toRow;
  fromCol -= 'a';
  fromRow = '8' - fromRow;
  toCol -= 'a';
  toRow = '8' - toRow;
  for(int i = 0; i < size; i++)
  {
    if(moves[i].fromCol() == fromCol  && moves[i].fromRow() == fromRow && moves[i].toCol() == toCol  && moves[i].toRow() == toRow)
    {
      return moves+i;
    }
  }
  return NULL;
}

double Kobold::abqsearch(ChessBoard* position, int depth, int qdepth, double alpha, double beta, bool loud, int ply)
{
  bool draw, dummy;
  if(position->gameOver(draw, dummy))
  {
    delete position;
    if(draw) return 0;
    return -std::numeric_limits<double>::infinity();
  }
  if(depth == 0 && ((qdepth == 0) || !loud))
  {
    double result = evaluate(position);
    delete position;
    return result;
  }
  else
  {
    double best = -std::numeric_limits<double>::infinity();
    Move* moves = NULL;
    int size = position->getMoves(moves);
    
    //position the moves
    //order is promotion->capture->history table->other
    int left = 0;
    for(int i = left; i < size; i++)
    {
      if(moves[i].isPromotion())
      {
        Move tmp = moves[i];
        moves[i] = moves[left];
        moves[left] = tmp;
        left++;
      }
    }
    for(int i = left; i < size; i++)
    {
      if(moves[i].isCapture())
      {
        Move tmp = moves[i];
        moves[i] = moves[left];
        moves[left] = tmp;
        left++;
      }
    }
    for(int i = left; i < size; i++)
    {
      if(moves[i] == killers[ply].k1 || moves[i] == killers[ply].k2)
      {
        Move tmp = moves[i];
        moves[i] = moves[left];
        moves[left] = tmp;
        left++;
      }
    }
    int i = 0; //needed after the loop for killer move checking
    for(; i < size && best < beta; i++)
    {
      double value;
      if(best > alpha)
        alpha = best;
      if(depth)
        value = -abqsearch(position->move(moves+i), depth-1, qdepth, -beta, -alpha, moves[i].isCapture(), ply+1);
      else
        value = -abqsearch(position->move(moves+i), 0, qdepth-1, -beta, -alpha, moves[i].isCapture(), ply+1);
      if(value > best)
        best = value;
    }
    //if the move caused a prune, add it to the killer moves of this depth
    if(best >= beta)
    {
      if(!(moves[i-1].isPromotion() || moves[i-1].isCapture()))
      {
        if(!(moves[i-1] == killers[ply].k1))
        {
          killers[ply].k2 = killers[ply].k1;
          killers[ply].k1 = moves[i-1];
        }
      }
    }
    position->freeMoves(moves);
    delete position;
    return best;
  }
}

Move* Kobold::play()
{
  double time =  timer.timeLeft() / 20.;
  time = time < 10 ? time : 10;
  double goalTime = timer.timeLeft() - time;
  cout << evaluatePawns(position) << endl;
  cout << position->getFen() << endl;
  cout << (int)position->myboard.getEnPassantStatus() << endl;
  cout << timer.timeLeft() << " seconds left" << endl;
  timer.start();
  
  static Move* moves = NULL;
  
  if(opening)
  {
    if(moves)
      position->freeMoves(moves);
    short size = position->getMoves(moves);
    Move* move = open(size, moves);
    if(move)
    {
      cout << "Following my conscience." << endl;
      return move;
    }
    //if move is NULL, we're out of the opening
    opening = false;
  }
  
  double best = -std::numeric_limits<double>::infinity();
  int chosen = 0;
  
  int depth;
  
  for(depth = 0; timer.timeLeft() > goalTime; depth++)
  {
    best = -std::numeric_limits<double>::infinity();
    int newChosen = 0;
    if(moves)
      position->freeMoves(moves);
    int size = position->getMoves(moves);
    //order the moves by likelihood to cause prunings down the road
    int left = 0;
    for(int i = left; i < size; i++)
    {
      if(moves[i].isPromotion())
      {
        Move tmp = moves[i];
        moves[i] = moves[left];
        moves[left] = tmp;
        left++;
      }
    }
    for(int i = left; i < size; i++)
    {
      if(moves[i].isCapture())
      {
        Move tmp = moves[i];
        moves[i] = moves[left];
        moves[left] = tmp;
        left++;
      }
    }
    for(int i = left; i < size; i++)
    {
      if(moves[i] == killers[0].k1 || moves[i] == killers[0].k2)
      {
        Move tmp = moves[i];
        moves[i] = moves[left];
        moves[left] = tmp;
        left++;
      }
    }
    for(int i = 0; i < size; i++)
    {
      double value = -abqsearch(position->move(moves+i), depth, 4, -std::numeric_limits<double>::infinity(), -best, moves[i].isCapture(), 1);
      if(value > best)
      {
        best = value;
        newChosen = i;
        if(!(moves[i-1].isPromotion() || moves[i-1].isCapture()))
        {
          if(!(moves[i-1] == killers[0].k1))
          {
            killers[0].k2 = killers[0].k1;
            killers[0].k1 = moves[i-1];
          }
        }
      }
      if(timer.timeLeft() <= goalTime) goto end; //that's right grader, I used goto to break out of 2 for loops, what you gonna do about it? It's a valid use no other construct in C++ can as well perform!
    }
    chosen = newChosen;
  }
  end:
  timer.stop();
  cout << "Ran to depth " << depth+1 << endl;
  if(timer.timeLeft() <= 0) return NULL;
  return moves + chosen;
}

Kobold::Kobold(double t, bool s) : AI()
{
  timer.setTime(t);
  opening = true;
  smart = s;
}
